package com.weimob.mcloud.wechatwork.customer.redis;

import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.geo.Circle;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoResults;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.connection.RedisGeoCommands;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.support.atomic.RedisAtomicLong;
import org.springframework.scripting.support.ResourceScriptSource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * @author baiyu
 * @description: RedisService
 * @date: 2019/5/8
 */
@Slf4j
@Service
@SuppressWarnings("unchecked")
public class RedisService {
    /**
     * 删除key最大值限制
     */
    public static final Integer DEL_LIMIT = 100;

    /**
     * 强制解锁时间设置
     */
    private static final long LOCK_TIME = 2L;

    /**
     * 等待时间
     **/
    private static final long WAIT_TIME = 3L;

    /**
     * 休眠时间
     **/
    private static final long SLEEP_TIME = 100;

    @Resource(name = "redisTemplate")
    private RedisTemplate redisTemplate;

    @Resource(name = "stringRedisTemplate")
    private RedisTemplate stringRedisTemplate;

    private DefaultRedisScript<Set> redisScript;


    @PostConstruct
    public void init() {
        redisScript = new DefaultRedisScript<Set>();
        redisScript.setResultType(Set.class);
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("redis-zset-diff.lua")));
    }

    /**
     * 设置set值 参数过期时间 过期时间类型
     *
     * @param key
     * @param value
     * @param expire
     * @param timeUnit
     */
    public void setString(String key, String value, long expire, TimeUnit timeUnit) {
        stringRedisTemplate.opsForValue().set(key, value, expire, timeUnit);
    }

    /**
     * setNx且设置过期时间
     *
     * @param key
     * @param value
     * @param expire
     * @param timeUnit
     * @return
     */
    public Boolean setNXAndExpire(String key, String value, long expire, TimeUnit timeUnit) {
        return stringRedisTemplate.opsForValue()
                .setIfAbsent(key, value, expire, timeUnit);
    }

    /**
     * 不存在新增，存在修改
     */
    public void setIfAbsent(Object key, Object value) {
        redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    public String getString(String key) {
        Object value = stringRedisTemplate.opsForValue().get(key);
        return null == value ? null : value.toString();
    }

    /**
     * 获取原来值并赋值
     */
    public Object getAndSet(Object key, Object value) {
        return stringRedisTemplate.opsForValue().getAndSet(key, value);
    }

    public Boolean remove(String key) {
        Boolean delete = redisTemplate.delete(key);
        return delete;
    }

    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    /**
     * 不存在会设置set值成功 参数过期时间 过期时间类型
     *
     * @param key
     * @param value
     * @param expire
     * @param timeUnit
     * @return
     */
    public boolean setNX(String key, String value, long expire, TimeUnit timeUnit) {
        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, value);
        if (Boolean.TRUE.equals(aBoolean)) {
            stringRedisTemplate.expire(key, expire, timeUnit);
        }
        return aBoolean == null ? false : aBoolean;
    }

    public boolean setNXNew(String key, String value, long expire, TimeUnit timeUnit) {

        try {
            Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, value, expire, timeUnit);
            log.info("加锁, key:{}, result:{}", key, aBoolean);
            return aBoolean == null ? false : aBoolean;
        } catch (Exception e) {
            log.error("加锁失败, key:{}, error:{}", key, e);
            return false;
        }
    }

    public boolean setNotExists(String key, String value, long expire, TimeUnit timeUnit) {

        Boolean aBoolean = stringRedisTemplate.opsForValue().setIfAbsent(key, value, expire, timeUnit);
        return aBoolean == null ? false : aBoolean;
    }

    /**
     * 释放锁
     *
     * @param key
     */
    public void releaseLock(String key) {
        stringRedisTemplate.delete(key);
    }

    public Long incr(String key, long expire, TimeUnit timeUnit) {
        RedisAtomicLong count = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        Long increment = count.getAndIncrement();
        boolean first = (null == increment || increment.longValue() == 0) && expire > 0;
        if (first) {
            //初始设置过期时间
            count.expire(expire, timeUnit);
        }
        return increment + 1;
    }

    public Long decr(String key, long expire, TimeUnit timeUnit) {
        RedisAtomicLong entityIdCounter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
        Long decrement = entityIdCounter.getAndDecrement();
        boolean first = (null == decrement || decrement.longValue() == 0) && expire > 0;
        if (first) {
            //初始设置过期时间
            entityIdCounter.expire(expire, timeUnit);
        }
        return decrement - 1;
    }

    public Long decr(String key, long value) {
        return redisTemplate.getConnectionFactory().getConnection().decrBy(
                redisTemplate.getKeySerializer().serialize(key), value);
    }


    /**
     * 判断key是否存在
     *
     * @param key
     * @return
     */
    public Boolean exist(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 用于存储指定地理空间位置
     *
     * @param key
     * @param mapValue eg：map.put("上海", new Point(121.454094,31.222095));
     * @param expire
     * @param timeUnit
     * @return
     */
    public long geoAdd(String key, Map mapValue, long expire, TimeUnit timeUnit) {
        long addFlag = 0;
        if (this.exist(key)) {
            Boolean deleteFlag = redisTemplate.delete(key);
            if (deleteFlag) {
                addFlag = this.geoAddAndExpire(key, mapValue, expire, timeUnit);
            }
        } else {
            addFlag = this.geoAddAndExpire(key, mapValue, expire, timeUnit);
        }
        return addFlag;
    }

    private long geoAddAndExpire(String key, Map mapValue, long expire, TimeUnit timeUnit) {
        long addFlag = redisTemplate.opsForGeo().add(key, mapValue);
        if (this.exist(key)) {
            redisTemplate.expire(key, expire, timeUnit);
        }
        return addFlag;
    }


    /**
     * 以给定的经纬度为中心， 返回键包含的位置元素当中， 与中心的距离不超过给定最大距离的所有位置元素
     *
     * @param key
     * @param point    Point(经度, 纬度) eg：new Point(121.50504,31.3543)
     * @param distance Distance(距离量, 距离单位) eg：new Distance(5000, Metrics.KILOMETERS)
     * @param limit
     * @return
     */
    public GeoResults geoRadius(String key, Point point, Distance distance, int limit) {
        Circle circle = new Circle(point, distance);
        //具体所需出参可调整
        RedisGeoCommands.GeoRadiusCommandArgs args = RedisGeoCommands.GeoRadiusCommandArgs.newGeoRadiusArgs().includeDistance().includeCoordinates().sortAscending().limit(limit);
        GeoResults<RedisGeoCommands.GeoLocation<String>> results = redisTemplate.opsForGeo()
                .radius(key, circle, args);
        return results;
    }

    /**
     * 删除key
     *
     * @param key
     * @return
     */
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 获取所有键值对
     *
     * @param key
     * @return
     */
    public Map<Object, Object> hGetAll(String key) {
        return redisTemplate.opsForHash().entries(key);
    }

    /**
     * 获取单个键的值
     *
     * @param key
     * @return
     */
    public Object hGet(String key, Object filed) {
        return redisTemplate.opsForHash().get(key, filed);
    }

    /**
     * 指定字段整数值加上增量
     *
     * @param key
     * @param field
     * @return
     */
    public Long hashIncrBy(String key, Object field) {
        return redisTemplate.opsForHash().increment(key, field, 1);
    }

    public Long hashIncr(String key, Object field, Integer num) {
        return redisTemplate.opsForHash().increment(key, field, num);
    }

    /**
     * 删除一个或多个hash字段
     *
     * @param key
     * @param fields
     * @return
     */
    public Long hashDelete(String key, Object... fields) {
        return redisTemplate.opsForHash().delete(key, fields);
    }

    /**
     * 存set值并设置过期时间
     */
    public Boolean setAdd(String key, String value, long expire, TimeUnit timeUnit) {
        redisTemplate.opsForSet().add(key, value);
        return redisTemplate.expire(key, expire, timeUnit);
    }

    /**
     * 获取set的长度
     */
    public Long getSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    /**
     * 存set值
     */
    public void setAdd(String key, String value) {
        redisTemplate.opsForSet().add(key, value);
    }

    /**
     * scan 实现
     *
     * @param redisTemplate redisTemplate
     * @param pattern       表达式，如：abc*，找出所有以abc开始的键
     */
    public List<String> scan(RedisTemplate<String, String> redisTemplate, String pattern) {
        return redisTemplate.execute((RedisCallback<List<String>>) connection -> {
            List<String> list = new ArrayList<>();
            try (Cursor<byte[]> cursor = connection.scan(new ScanOptions.ScanOptionsBuilder()
                    .match(pattern)
                    .count(10000).build())) {
                while (cursor.hasNext()) {
                    list.add(new String(cursor.next(), StandardCharsets.UTF_8));
                }
            } catch (Exception e) {
                log.error("redisService scan error", e);
            }
            return list;
        });
    }

    /**
     * 删除key
     *
     * @param keys
     */
    public Long remove(List<String> keys) {
        //list切割 避免delete 超过100
        if (CollectionUtils.isEmpty(keys)) {
            return 0L;
        }
        log.info("remove redis key total count:{}", keys.size());
        int count = keys.size();
        Long successCount=0L;
        int totalPageNum = (int) Math.ceil((double) count / DEL_LIMIT);
        for (int i = 0; i < totalPageNum; i++) {
            List<String> list = keys.subList(DEL_LIMIT * i, Math.min((i + 1) * DEL_LIMIT, count));
            Long delete = redisTemplate.delete(list);
            successCount = successCount + delete;
        }
        log.info("remove redis key success count:{}", successCount);
        return successCount;
    }
}
